#include <map>
#include <string>
#include "Animation.h"
#include "AnimatorHolder.h"
#include "AnimationFilmHolder.h"
#include "Timer.h"
#include "Brutus.h"
#include "CollisionChecker.h"
#include "Popeye.h"
#include "AI.h"
#include "Scores.h"

Brutus::Brutus(Dim _x, Dim _y, AnimationFilm* film, 
	FrameRangeAnimation *_walkAnimation, FrameRangeAnimator *_walkAnimator,
	FrameRangeAnimation *_punchUpAnimation, FrameRangeAnimator *_punchUpAnimator,
	FrameRangeAnimation *_punchDownAnimation, FrameRangeAnimator *_punchDownAnimator,
	MovingAnimation *_fallAnimation, MovingAnimator *_fallAnimator): 
	Sprite(_x,_y,film),
	walkAnimation(_walkAnimation), walkAnimator(_walkAnimator),
	punchUpAnimation(_punchUpAnimation), punchUpAnimator(_punchUpAnimator),
	punchDownAnimation(_punchDownAnimation), punchDownAnimator(_punchDownAnimator),
	fallAnimation(_fallAnimation), fallAnimator(_fallAnimator) {
	frameNo = currFilm->GetTotalFrames();
	SetFrame(0);
	canMove = true;
	isFalling = false;
	isClimbing = false;
	goToPopeye = false;
	climbDown = false;
	climbUp = false;
	direction = 1;
	tickAnimation = new TickAnimation(0, 800, 0, LookForPopeye, this);
	timerTick = new TimerTickAnimator(tickAnimation);
	AnimatorHolder::Register(timerTick);
	AnimatorHolder::MarkAsRunning(timerTick);
	isHit = false;
	flashAnimation = new FlashingAnimation(15, 100, 200, 0);
	flashAnimator = new FlashingAnimator();
	AnimatorHolder::Register(flashAnimator);
	hitAnimation = new MovingAnimation(20, 0, 50, true, 0);
	hitAnimator = new MovingAnimator();
	AnimatorHolder::Register(hitAnimator);
}

Brutus::~Brutus(void) { }

bool Brutus::OnPlatform() {
	Platform *p = PlatformHolder::CheckPlatformCollision(this);
	if(p) return true;
	return false;
}

void Brutus::LookForPopeye(void *closure) {
	Brutus * b = (Brutus *) closure;

	b->goToPopeye = true;
}

void Brutus::GoToPopeye(void) {
	Rect popeyeRect = AI::getPopeyeRect();
	goToPopeye = false;
	if(OnSamePlatform(AI::getPopeyePlatform())) {
		if(areNear(popeyeRect)) {
			ThrowBottle();
			StopMoving();
		}
		else 
			MoveHorizontal(direction*10);
	}
	else {
		if(popeyeRect.y < 220) {
			if(areNear(popeyeRect))
				PunchUp();
			else
				MoveHorizontal(direction*10);
		}
		else {
			if(isHigher(popeyeRect) && GetY() > 232) {
				if(areNear(popeyeRect)) {
					PunchUp();
				}
				else {
					Stair *stair = ((Platform*)GetPlatform())->CollidesWithGoingUpStair(this);
					if(stair && ((Stair *)stair)->GetStairType().compare("VerticalStair") != 0) {
						climbUp = true;
						MoveUp();
					}
					else MoveHorizontal(direction*10);
				}
			}
			else {
				if(areNear(popeyeRect)) {
					PunchDown();
				}
				else {
					Stair *stair = ((Platform*)GetPlatform())->CollidesWithGoingDownStair(this);
					if(stair && ((Stair *)stair)->GetStairType().compare("VerticalStair") != 0) {
						climbDown = true;
						MoveDown();
					}
					else MoveHorizontal(direction*10);
				}
			}
		}
	}
}

void Brutus::RunAway(void) {
	Rect popeyeRect = AI::getPopeyeRect();
	goToPopeye = false;

	if(popeyeRect.x < this->GetX()) direction = 1;
	else direction = -1;

	if(isHigher(popeyeRect) || OnSamePlatform(AI::getPopeyePlatform())) {
		Stair *stair = ((Platform*)GetPlatform())->CollidesWithGoingUpStair(this);
		if(stair && ((Stair *)stair)->GetStairType().compare("VerticalStair") != 0) {
			climbDown = true;
			MoveDown();
			return;
		}
		else {
			MoveHorizontal(direction*10);
			return;
		}
	}
	else if(!isHigher(popeyeRect) && GetY() > 232) {
		Stair *stair = ((Platform*)GetPlatform())->CollidesWithGoingDownStair(this);
		if(stair && ((Stair *)stair)->GetStairType().compare("VerticalStair") != 0) {
			climbUp = true;
			MoveUp();
			return;
		}
		else {
			MoveHorizontal(direction*10);
			return;
		}
	}
	MoveHorizontal(direction*10);
}

bool Brutus::OnSamePlatform(Platform *plat) {
	if(plat == this->platform) return true;
	return false;
}

bool Brutus::areNear(Rect rect) {
	int distance = rect.x-this->GetX();
	if(distance*direction < 0) {
		ChangeDirection();
	}
	if(distance*direction < 30)
		return true;

	return false;
}

bool Brutus::isHigher(Rect rect) {
	int distance = (rect.y-rect.h)-(this->GetRect().y-this->GetRect().h);
	if(distance < 0)
		return true;
	return false;
}

void Brutus::ChangeDirection(void) {
	direction *= -1;
}

void Brutus::GenerateNextMove(void *closure) {
	Brutus *player = (Brutus *) closure;

	if(player->IsHit()) return;
	
	if(!player->canMove) {
		if(player->climbDown)
			player->MoveDown();
		if(player->climbUp)
			player->MoveUp();
		return;
	}
	
	if(player->goToPopeye) {
		if(AI::getPopeyeMode() == normal)
			player->GoToPopeye();
		else
			player->RunAway();
	}
	else
		player->StopMoving();
}

void Brutus::ThrowBottle(void) {
	printf("Supposed to throw bottle here...\n");
}

void Brutus::StopWalking(Animator *animator, void *closure) {
	Brutus *player = (Brutus *)closure;

	AnimatorHolder::DelayedSuspention(animator);
}

//Horizontal movement
void Brutus::MoveHorizontal(int dx) {
	if(!canMove) return;
	SetWalkFilm();
	
	if (!isFalling) {
		SpinachBox *sbox = ((Platform *)platform)->CollidesWithSpinachBox(this, dx);
		if(sbox) {
			sbox->AlignLeftOf(this);
			ChangeDirection();
			StopMoving();
			dx = 0;
			return;
		}
		if(((Platform *)platform)->CollidesWithEdge(this, dx)) {
			ChangeDirection();
			StopMoving();
			dx = 0;
			return;
		}
		if(walkAnimator->HasFinished()) {
			walkAnimator->Start(this, walkAnimation, Timer::getgametime());
			walkAnimator->SetOnFinish(StopWalking, (void*)this);
			AnimatorHolder::MarkAsRunning(walkAnimator);
			if(!CollisionChecker::collidesWithBoarders(this))
				walkAnimation->SetDx(dx);
			else {
				walkAnimation->SetDx(0);
				dx = 0;
			}

			if(direction < 0) 
				walkAnimation->SetFlipH(true);
			else if(direction > 0)
				walkAnimation->SetFlipH(false);
		}
	}
	if (!OnPlatform()) {
		StopMoving();
		isFalling = true;
		NotifyStartFalling();
	}
}

void Brutus::StopMoving(void) {
	if(!walkAnimator->HasFinished()) {
		walkAnimator->Stop();
		AnimatorHolder::MarkAsSuspended(walkAnimator);
		walkAnimation->SetDx(0);
	}
}


void Brutus::Move(int dx, int dy) {

	if(isHit) {
		if(GetX() > SCREEN_WIDTH+40 || GetX() < 0-40) {
			hitAnimator->Stop();
			AnimatorHolder::DelayedSuspention(hitAnimator);
			ResetToDefaultFilm();
			this->SetX(400);
			this->SetY(408-this->GetRect().h); //harcoded values, tsk tsk
			this->SetPlatform(PlatformHolder::CheckPlatformCollision(this));
			((Platform *)platform)->AlignWithPlatform(this);
			flashAnimator->SetOnFinish(ResetEnd, this);
			flashAnimator->Start(this, flashAnimation, Timer::getgametime());
			AnimatorHolder::MarkAsRunning(flashAnimator);
		}
	}
	else if (!isFalling && !isClimbing) {
		if(!platform) {
			Platform *p = PlatformHolder::CheckPlatformCollision(this);
			if (p) {
				this->platform = p;
				p->AlignWithPlatform(this);
				climbDown = false;
				climbUp = false;
			}
		}
		else {
			Platform *p = (Platform *) platform;
			p->AlignWithPlatform(this);
			climbDown = false;
			climbUp = false;
		}
	}
	else if(isFalling) {
		Platform *p = PlatformHolder::CheckPlatformCollision(this);
		if (p) {
			dy = 0;
			this->platform = p;
			p->AlignWithPlatform(this);
			NotifyStopFalling();
		}
		else
			dy += ++velocity;
	}

	Sprite::Move(dx, dy);
}

void Brutus::NotifyStartFalling() {
	velocity = 0;
	DisableMovement();
	fallAnimator->Start(this, fallAnimation, Timer::getgametime());
	AnimatorHolder::MarkAsRunning(fallAnimator);
}

void Brutus::NotifyStopFalling() {
	isFalling = false;
	EnableMovement();
	fallAnimator->Stop();
	AnimatorHolder::DelayedSuspention(fallAnimator);
}

//Vertical movement
void Brutus::MoveUp(void) {
	StopMoving();
	if (canMove) {
		if (platform) {
			if (stair = ((Platform*)platform)->CollidesWithGoingUpStair(this)) {
				if(((Stair *)stair)->GetStairType().compare("VerticalStair") == 0) {
					MoveHorizontal(direction*10);
					return; //Brurus does not do vertical stairs, too cool for that
				}
				else
					SetDiagonalClimbUpFilm();
				isClimbing = true;
				platform = (Platform *) 0;
				((Stair *)stair)->AlignUp(this);
				((Stair *)stair)->AnimateUp(this);
			}
		}
	}
	else if (stair) {
		SetDiagonalClimbUpFilm();
		((Stair *)stair)->AnimateUp(this);
	}
}

void Brutus::MoveDown(void) {
	StopMoving();
	if (canMove) {
		if (platform) {
			if (stair = ((Platform*)platform)->CollidesWithGoingDownStair(this)) {
				if(((Stair *)stair)->GetStairType().compare("VerticalStair") == 0) {
					MoveHorizontal(direction*10);
					return;
				}
				else
					SetDiagonalClimbDownFilm();
				isClimbing = true;
				platform = (Platform *) 0;
				((Stair *)stair)->AlignDown(this);
				((Stair *)stair)->AnimateDown(this);
			}
		}
	}
	else {
		SetDiagonalClimbDownFilm();
		((Stair *)stair)->AnimateDown(this);
	}
}

void Brutus::PunchCompleted(Animator *animator, void *closure) {
	Brutus *player = (Brutus *)closure;
	player->EnableMovement();
	//player->SetY(player->GetY()+10); //This only works for brutus, brutus will need another value
	AnimatorHolder::DelayedSuspention(animator);
	player->SetWalkFilm();
}

void Brutus::PunchUp(void) {
	if(isClimbing) return;
	if(punchUpAnimator->HasFinished()) {
		DisableMovement();
		SetPunchUpFilm();
		SetY(GetY()-20);
		punchUpAnimator->SetOnFinish(PunchCompleted, (void*)this);
		punchUpAnimator->Start(this, punchUpAnimation, Timer::getgametime());
		AnimatorHolder::MarkAsRunning(punchUpAnimator);
	}
}

void Brutus::PunchDown(void) {
	if(isClimbing) return;
	if(punchDownAnimator->HasFinished()) {
		DisableMovement();
		SetPunchDownFilm();
		SetY(GetY()+20);
		punchDownAnimator->SetOnFinish(PunchCompleted, (void*)this);
		punchDownAnimator->Start(this, punchDownAnimation, Timer::getgametime());
		AnimatorHolder::MarkAsRunning(punchDownAnimator);
	}
}

void Brutus::ResetStart(Animator *anim, void *closure) {
	//dummy?
}

void Brutus::ResetEnd(Animator *anim, void *closure) {
	Brutus *b = (Brutus*)closure;
	b->SetHit(false);
	anim->Stop();
	AnimatorHolder::DelayedSuspention(anim);
	b->SetVisibility(true);
}

void Brutus::CollisionAction(Sprite *s) {
	//Brutus should collide with popeye
	Popeye *p = (Popeye *) s;
	if(p->getMode() == spinach && p->Punches()) {
		if(isHit) return;
		StopMoving();
		SetHitFilm();
		//flashAnimator->SetOnFinish(ResetEnd, this);
		if(p->GetX() > GetX())
			hitAnimation->SetFlipH(false);
		else
			hitAnimation->SetFlipH(true);
		hitAnimator->Start(this, hitAnimation, Timer::getgametime());
		AnimatorHolder::MarkAsRunning(hitAnimator);
		isHit = true;
		Scores::increaseScore(1000);
		//brutus dies here...
	}
	else if(p->getMode() == normal) {
		p->GotHit();
	}
};

void Brutus::ResetToDefaultFilm(void) {
	currFilm = (AnimationFilm *) AnimationFilmHolder::GetSingleton()->GetFilm("brutus_walk");
} 

void Brutus::SetHitFilm(void) {
	currFilm = (AnimationFilm *) AnimationFilmHolder::GetSingleton()->GetFilm("brutus_hit");
}

void Brutus::SetWalkFilm(void) {
	currFilm = (AnimationFilm *) AnimationFilmHolder::GetSingleton()->GetFilm("brutus_walk");
}

void Brutus::SetPunchUpFilm(void) {
	currFilm = (AnimationFilm *)AnimationFilmHolder::GetSingleton()->GetFilm("brutus_punch_up");
};

void Brutus::SetPunchDownFilm(void) {
	currFilm = (AnimationFilm *)AnimationFilmHolder::GetSingleton()->GetFilm("brutus_punch_down");
};

void Brutus::SetDiagonalClimbUpFilm(void) {
	currFilm = (AnimationFilm *)AnimationFilmHolder::GetSingleton()->GetFilm("brutus_climb_stairs");
};
	
void Brutus::SetDiagonalClimbDownFilm(void) {
	currFilm = (AnimationFilm *)AnimationFilmHolder::GetSingleton()->GetFilm("brutus_climb_stairs");
};